﻿using Dycox.Linq.Data;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;

namespace Dycox.Workflows
{
    [DataContract]
    public class WorkflowUndoState
    {
        [JsonConstructor]
        private WorkflowUndoState()
        {

        }

        internal WorkflowUndoState(WFProcess process)
        {
            if (process == null)
                throw new ArgumentNullException("process");

            _process = process.Clone();
        }

        [DataMember(Name = "wp")]
        private WFProcess _process;

        [DataMember(Name = "d")]
        List<object> _deletes;

        [DataMember(Name = "u")]
        List<EntityChangeInfo> _updates;

        [DataMember(Name = "i")]
        List<object> _inserts;

        public WFProcess Process
        {
            get { return _process; }
        }

        internal EntityChangeInfo[] Updates
        {
            get
            {
                if (_updates != null)
                    return _updates.ToArray();
                else
                    return new EntityChangeInfo[0];
            }
        }

        internal object[] Inserts
        {
            get
            {
                if (_inserts != null)
                    return _inserts.ToArray();
                else
                    return null;
            }
        }

        internal object[] Deletes
        {
            get
            {
                if (_deletes != null)
                    return _deletes.ToArray();
                else
                    return null;
            }
        }

        private bool ShouldExclude(object obj)
        {
            IWorkflowComponent c = obj as IWorkflowComponent;

            if (c != null)
            {
                return c.ID.ProcessId == _process.ProcessId;
            }
            else if (obj is WFFrame)
                return true;
            else
                return false;
        }

        internal void AddInsert(object obj)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");

            if (!ShouldExclude(obj))
            {
                if (_inserts == null)
                    _inserts = new List<object>();
                if (!_inserts.Contains(obj))
                    _inserts.Add(obj);
            }
        }

        internal void AddUpdate(EntityChangeInfo info)
        {
            if (info == null)
                throw new ArgumentNullException("info");

            if (!ShouldExclude(info.New))
            {
                if (_updates == null)
                    _updates = new List<EntityChangeInfo>();
                else if (_updates.Any(o => object.Equals(o.New, info.New)))
                    return;

                _updates.Add(info);
            }
        }

        internal void AddDelete(object obj)
        {
            if (obj == null)
                throw new ArgumentNullException("obj");

            if (_deletes == null)
                _deletes = new List<object>();

            if (!_deletes.Contains(obj))
                _deletes.Add(obj);
        }

        internal string Serialize()
        {
            JsonSerializerSettings jss = new JsonSerializerSettings();
            jss.TypeNameHandling = TypeNameHandling.All;
            jss.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;

            return JsonConvert.SerializeObject(this, jss);
        }

        internal static WorkflowUndoState Deserialize(string data)
        {
            JsonSerializerSettings jss = new JsonSerializerSettings();
            jss.TypeNameHandling = TypeNameHandling.All;
            jss.TypeNameAssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
            return JsonConvert.DeserializeObject<WorkflowUndoState>(data, jss);
        }

        internal byte[] Compress()
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (GZipStream gzip = new GZipStream(ms, CompressionMode.Compress, false))
                using (StreamWriter sw = new StreamWriter(gzip))
                {
                    string content = this.Serialize();
                    sw.Write(content);
                }

                return ms.ToArray();
            }
        }

        internal static WorkflowUndoState Decompress(byte[] data)
        {
            using (MemoryStream ms = new MemoryStream(data))
            {
                using (GZipStream gzip = new GZipStream(ms, CompressionMode.Decompress, false))
                using (StreamReader sr = new StreamReader(gzip))
                {
                    string content = sr.ReadToEnd();
                    return Deserialize(content);
                }
            }
        }

        private IEnumerable<object> OriginalObjects
        {
            get
            {
                IEnumerable<object> coll;

                if (_updates != null)
                    coll = _updates.Select(o => o.Original);
                else
                    coll = null;

                if (_deletes != null)
                {
                    if (coll == null) coll = _deletes;
                    else coll = coll.Union(_deletes);
                }

                return coll;
            }
        }

        private IEnumerable<object> NewObjects
        {
            get
            {
                IEnumerable<object> coll = _inserts;

                if (_updates != null)
                {
                    if (coll != null)
                        coll = coll.Union(_updates.Select(o => o.New));
                    else
                        coll = _updates.Select(o => o.New);
                }

                return coll;
            }
        }

        public T GetOriginalEntity<T>()
        {
            var coll = OriginalObjects;

            if (coll != null) return coll.OfType<T>().SingleOrDefault();
            else return default(T);
        }

        public T GetOriginalEntity<T>(Func<T, bool> predict)
        {
            var coll = OriginalObjects;
            if (coll != null)
                return coll.OfType<T>().FirstOrDefault(predict);
            else
                return default(T);
        }

        public T GetNewEntity<T>()
        {
            var coll = NewObjects;

            if (coll != null) return coll.OfType<T>().SingleOrDefault();
            else return default(T);
        }

        public T GetNewEntity<T>(Func<T, bool> predict)
        {
            var coll = NewObjects;
            if (coll != null)
                return coll.OfType<T>().FirstOrDefault(predict);
            else
                return default(T);
        }


    }
}
